/*******************************************************************************
* Copyright (c) 2010, 2016 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Tom Hochstein (Freescale) - Bug 393703: NotHandledException selecting inactive command under 'Previous Choices' in Quick access
* Lars Vogel <Lars.Vogel@vogella.com> - Bug 428050, 472654
* Brian de Alwis - Fix size computation to account for trim
* Markus Kuppe <bugs.eclipse.org@lemmster.de> - Bug 449485: [QuickAccess] "Widget is disposed" exception in errorlog during shutdown due to quickaccess.SearchField.storeDialog
* Elena Laskavaia <elaskavaia.cdt@gmail.com> - Bug 433746: [QuickAccess] SWTException on closing quick access shell
* Patrik Suzzi <psuzzi@gmail.com> - Bug 488926, 491278, 491291, 491312, 491293, 436788, 513436
******************************************************************************/
package org.eclipse.ui.internal.quickaccess;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ParameterizedCommand;
import org.eclipse.core.expressions.EvaluationResult;
import org.eclipse.core.expressions.Expression;
import org.eclipse.core.expressions.ExpressionInfo;
import org.eclipse.core.expressions.IEvaluationContext;
import org.eclipse.core.runtime.Assert;
import org.eclipse.e4.core.commands.ECommandService;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.core.di.annotations.Optional;
import org.eclipse.e4.core.di.extensions.Preference;
import org.eclipse.e4.ui.bindings.internal.BindingTableManager;
import org.eclipse.e4.ui.bindings.internal.ContextSet;
import org.eclipse.e4.ui.model.application.MApplication;
import org.eclipse.e4.ui.model.application.ui.basic.MPart;
import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
import org.eclipse.e4.ui.workbench.IPresentationEngine;
import org.eclipse.e4.ui.workbench.modeling.EPartService;
import org.eclipse.jface.bindings.Binding;
import org.eclipse.jface.bindings.TriggerSequence;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.util.Geometry;
import org.eclipse.jface.util.Util;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.accessibility.ACC;
import org.eclipse.swt.accessibility.AccessibleAdapter;
import org.eclipse.swt.accessibility.AccessibleEvent;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Monitor;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.ISources;
import org.eclipse.ui.IWorkbenchCommandConstants;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.keys.IBindingService;
import org.eclipse.ui.swt.IFocusService;
public class SearchField {
private static final String QUICK_ACCESS_COMMAND_ID = "org.eclipse.ui.window.quickAccess"; //$NON-NLS-1$
private static final String TEXT_ARRAY = "textArray"; //$NON-NLS-1$
private static final String TEXT_ENTRIES = "textEntries"; //$NON-NLS-1$
private static final String ORDERED_PROVIDERS = "orderedProviders"; //$NON-NLS-1$
private static final String ORDERED_ELEMENTS = "orderedElements"; //$NON-NLS-1$
private static final int MAXIMUM_NUMBER_OF_ELEMENTS = 60;
private static final int MAXIMUM_NUMBER_OF_TEXT_ENTRIES_PER_ELEMENT = 3;
private static final String DIALOG_HEIGHT = "dialogHeight"; //$NON-NLS-1$
private static final String DIALOG_WIDTH = "dialogWidth"; //$NON-NLS-1$
Shell shell;
private Text txtQuickAccess;
private QuickAccessContents quickAccessContents;
private MWindow window;
private Map<String, QuickAccessProvider> providerMap = new HashMap<>();
private Map<String, QuickAccessElement> elementMap = new HashMap<>();
private Map<QuickAccessElement, ArrayList<String>> textMap = new HashMap<>();
private LinkedList<QuickAccessElement> previousPicksList = new LinkedList<>();
private int dialogHeight = -1;
private int dialogWidth = -1;
private Control previousFocusControl;
boolean activated = false;
@Inject
private EPartService partService;
private Table table;
private String selectedString = ""; //$NON-NLS-1$
private AccessibleAdapter accessibleListener;
@Inject
private IBindingService bindingService;
private TriggerSequence triggerSequence = null;
@PostConstruct
void createControls(final Composite parent, MApplication application, MWindow window) {
this.window = window;
final Composite comp = new Composite(parent, SWT.NONE);
comp.setLayout(new GridLayout());
txtQuickAccess = createText(comp);
updateQuickAccessText();
parent.getShell().addControlListener(new ControlListener() {
@Override
public void controlResized(ControlEvent e) {
closeDropDown();
}
@Override
public void controlMoved(ControlEvent e) {
closeDropDown();
}
private void closeDropDown() {
if (shell == null || shell.isDisposed() || txtQuickAccess.isDisposed() || !shell.isVisible())
return;
quickAccessContents.doClose();
}
});
hookUpSelectAll();
final CommandProvider commandProvider = new CommandProvider();
QuickAccessProvider[] providers = new QuickAccessProvider[] {
new PreviousPicksProvider(previousPicksList),
new EditorProvider(), new ViewProvider(application, window),
new PerspectiveProvider(), commandProvider, new ActionProvider(),
new WizardProvider(), new PreferenceProvider(), new PropertiesProvider() };
for (QuickAccessProvider provider : providers) {
providerMap.put(provider.getId(), provider);
}
restoreDialog();
quickAccessContents = new QuickAccessContents(providers) {
@Override
protected void updateFeedback(boolean filterTextEmpty, boolean showAllMatches) {
}
@Override
protected void doClose() {
txtQuickAccess.setText(""); //$NON-NLS-1$
resetProviders();
dialogHeight = shell.getSize().y;
dialogWidth = shell.getSize().x;
shell.setVisible(false);
removeAccessibleListener();
}
@Override
protected QuickAccessElement getPerfectMatch(String filter) {
return elementMap.get(filter);
}
@Override
protected void handleElementSelected(String string, Object selectedElement) {
if (selectedElement instanceof QuickAccessElement) {
QuickAccessElement element = (QuickAccessElement) selectedElement;
addPreviousPick(string, element);
txtQuickAccess.setText(""); //$NON-NLS-1$
element.execute();
// after execution, the search box might be disposed
if (txtQuickAccess.isDisposed()) {
return;
}
/*
* By design, attempting to activate a part that is already
* active does not change the focus. However in the case of
* using Quick Access, focus is not in the active part, so
* re-activating the active part results in focus being left
* behind in the text field. If this happens then assign
* focus to the active part explicitly.
*/
if (txtQuickAccess.isFocusControl()) {
MPart activePart = partService.getActivePart();
if (activePart != null) {
IPresentationEngine pe = activePart.getContext().get(
IPresentationEngine.class);
pe.focusGui(activePart);
}
}
if (shell.isVisible()) {
// after selection, closes the shell
quickAccessContents.doClose();
}
}
}
};
quickAccessContents.hookFilterText(txtQuickAccess);
shell = new Shell(parent.getShell(), SWT.RESIZE | SWT.ON_TOP);
shell.setBackground(shell.getDisplay().getSystemColor(SWT.COLOR_WHITE));
shell.setText(QuickAccessMessages.QuickAccess_EnterSearch); // just for debugging, not shown anywhere
GridLayoutFactory.fillDefaults().applyTo(shell);
quickAccessContents.createHintText(shell, Window.getDefaultOrientation());
table = quickAccessContents.createTable(shell, Window.getDefaultOrientation());
txtQuickAccess.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent e) {
// release mouse button = click = CTRL+3 -> activate QuickAccess
showList();
}
});
txtQuickAccess.addFocusListener(new FocusListener() {
@Override
public void focusLost(FocusEvent e) {
// Once the focus event is complete, check if we should close the shell
table.getDisplay().asyncExec(() -> checkFocusLost(table, txtQuickAccess));
activated = false;
}
@Override
public void focusGained(FocusEvent e) {
IHandlerService hs = SearchField.this.window.getContext().get(IHandlerService.class);
if (commandProvider.getContextSnapshot() == null) {
commandProvider.setSnapshot(hs.createContextSnapshot(true));
}
previousFocusControl = (Control) e.getSource();
activated = true;
}
});
table.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
// Once the focus event is complete, check if we should close
// the shell
table.getDisplay().asyncExec(() -> checkFocusLost(table, txtQuickAccess));
}
});
txtQuickAccess.addModifyListener(e -> showList());
txtQuickAccess.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.keyCode == SWT.ESC) {
activated = false;
txtQuickAccess.setText(""); //$NON-NLS-1$
if (txtQuickAccess == previousFocusControl) {
txtQuickAccess.getShell().forceFocus();
} else if (previousFocusControl != null && !previousFocusControl.isDisposed())
previousFocusControl.setFocus();
} else if (e.keyCode == SWT.ARROW_UP) {
// Windows moves caret left/right when pressing up/down,
// avoid this as the table selection changes for up/down
e.doit = false;
} else if (e.keyCode == SWT.ARROW_DOWN) {
e.doit = false;
}
if (e.doit == false) {
// arrow key pressed
notifyAccessibleTextChanged();
}
}
});
quickAccessContents.createInfoLabel(shell);
}
@Inject
@Optional
protected void keybindingPreferencesChanged(
@SuppressWarnings("restriction") @Preference(nodePath = "org.eclipse.ui.workbench", value = "org.eclipse.ui.commands") String preferenceValue) {
if (preferenceValue != null) {
updateQuickAccessText();
}
}
private void showList() {
boolean wasVisible = shell.getVisible();
boolean nowVisible = txtQuickAccess.getText().length() > 0 || activated;
if (!wasVisible && nowVisible) {
layoutShell();
addAccessibleListener();
quickAccessContents.preOpen();
}
if (wasVisible && !nowVisible) {
removeAccessibleListener();
}
if (nowVisible) {
notifyAccessibleTextChanged();
}
shell.setVisible(nowVisible);
}
@Inject
private BindingTableManager manager;
@Inject
private ECommandService eCommandService;
@Inject
private IContextService contextService;
/**
* Compute the best binding for the command and sets the trigger
*
*/
protected void updateQuickAccessTriggerSequence() {
triggerSequence = bindingService.getBestActiveBindingFor(QUICK_ACCESS_COMMAND_ID);
// FIXME Bug 491701 - [KeyBinding] get best active binding is not working
if (triggerSequence == null) {
ParameterizedCommand cmd = eCommandService.createCommand(QUICK_ACCESS_COMMAND_ID, null);
ContextSet contextSet = manager.createContextSet(Arrays.asList(contextService.getDefinedContexts()));
Binding binding = manager.getBestSequenceFor(contextSet, cmd);
triggerSequence = (binding == null) ? null : binding.getTriggerSequence();
}
}
private Text createText(Composite parent) {
Text text = new Text(parent, SWT.SEARCH);
text.setMessage(QuickAccessMessages.QuickAccess_EnterSearch);
return text;
}
private void updateQuickAccessText() {
if (txtQuickAccess == null || txtQuickAccess.isDisposed()) {
return;
}
updateQuickAccessTriggerSequence();
if (triggerSequence != null) {
txtQuickAccess.setToolTipText(
NLS.bind(QuickAccessMessages.QuickAccess_TooltipDescription, triggerSequence.format()));
} else {
txtQuickAccess.setToolTipText(QuickAccessMessages.QuickAccess_TooltipDescription_Empty);
}
GC gc = new GC(txtQuickAccess);
// workaround for Bug 491317
if (Util.isWin32() || Util.isGtk()) {
FontMetrics fm = gc.getFontMetrics();
int wHint = QuickAccessMessages.QuickAccess_EnterSearch.length() * fm.getAverageCharWidth();
int hHint = fm.getHeight();
gc.dispose();
txtQuickAccess.setSize(txtQuickAccess.computeSize(wHint, hHint));
} else {
Point p = gc.textExtent(QuickAccessMessages.QuickAccess_EnterSearch);
Rectangle r = txtQuickAccess.computeTrim(0, 0, p.x, p.y);
gc.dispose();
// computeTrim() may result in r.x < 0
GridDataFactory.fillDefaults().hint(r.width - r.x, SWT.DEFAULT).applyTo(txtQuickAccess);
}
txtQuickAccess.requestLayout();
}
private void hookUpSelectAll() {
final IEclipseContext windowContext = window.getContext();
IFocusService focus = windowContext.get(IFocusService.class);
focus.addFocusTracker(txtQuickAccess, SearchField.class.getName());
Expression focusExpr = new Expression() {
@Override
public void collectExpressionInfo(ExpressionInfo info) {
info.addVariableNameAccess(ISources.ACTIVE_FOCUS_CONTROL_ID_NAME);
}
@Override
public EvaluationResult evaluate(IEvaluationContext context) {
return EvaluationResult.valueOf(SearchField.class.getName().equals(
context.getVariable(ISources.ACTIVE_FOCUS_CONTROL_ID_NAME)));
}
};
IHandlerService whService = windowContext.get(IHandlerService.class);
whService.activateHandler(IWorkbenchCommandConstants.EDIT_SELECT_ALL,
new AbstractHandler() {
@Override
public Object execute(ExecutionEvent event) {
txtQuickAccess.selectAll();
return null;
}
}, focusExpr);
whService.activateHandler(IWorkbenchCommandConstants.EDIT_CUT, new AbstractHandler() {
@Override
public Object execute(ExecutionEvent event) {
txtQuickAccess.cut();
return null;
}
}, focusExpr);
whService.activateHandler(IWorkbenchCommandConstants.EDIT_COPY, new AbstractHandler() {
@Override
public Object execute(ExecutionEvent event) {
txtQuickAccess.copy();
return null;
}
}, focusExpr);
whService.activateHandler(IWorkbenchCommandConstants.EDIT_PASTE, new AbstractHandler() {
@Override
public Object execute(ExecutionEvent event) {
txtQuickAccess.paste();
return null;
}
}, focusExpr);
}
/**
* This method was copy/pasted from JFace.
*/
private static Monitor getClosestMonitor(Display toSearch, Point toFind) {
int closest = Integer.MAX_VALUE;
Monitor[] monitors = toSearch.getMonitors();
Monitor result = monitors[0];
for (Monitor currentMonitor : monitors) {
Rectangle clientArea = currentMonitor.getClientArea();
if (clientArea.contains(toFind)) {
return currentMonitor;
}
int distance = Geometry.distanceSquared(Geometry.centerPoint(clientArea), toFind);
if (distance < closest) {
closest = distance;
result = currentMonitor;
}
}
return result;
}
/**
* This method was copy/pasted from JFace.
*/
private Rectangle getConstrainedShellBounds(Display display, Rectangle preferredSize) {
Rectangle result = new Rectangle(preferredSize.x, preferredSize.y, preferredSize.width,
preferredSize.height);
Point topLeft = new Point(preferredSize.x, preferredSize.y);
Monitor mon = getClosestMonitor(display, topLeft);
Rectangle bounds = mon.getClientArea();
if (result.height > bounds.height) {
result.height = bounds.height;
}
if (result.width > bounds.width) {
result.width = bounds.width;
}
result.x = Math.max(bounds.x, Math.min(result.x, bounds.x + bounds.width - result.width));
result.y = Math.max(bounds.y, Math.min(result.y, bounds.y + bounds.height - result.height));
return result;
}
void layoutShell() {
Display display = txtQuickAccess.getDisplay();
Rectangle tempBounds = txtQuickAccess.getBounds();
Rectangle compBounds = display.map(txtQuickAccess, null, tempBounds);
int preferredWidth = dialogWidth == -1 ? 350 : dialogWidth;
int width = Math.max(preferredWidth, compBounds.width);
int height = dialogHeight == -1 ? 250 : dialogHeight;
// If size would extend past the right edge of the shell, try to move it
// to the left of the text
Rectangle shellBounds = txtQuickAccess.getShell().getBounds();
if (compBounds.x + width > shellBounds.x + shellBounds.width){
compBounds.x = Math.max(shellBounds.x, (compBounds.x + compBounds.width - width));
}
shell.setBounds(getConstrainedShellBounds(display, new Rectangle(compBounds.x, compBounds.y
+ compBounds.height, width, height)));
shell.layout();
}
public void activate(Control previousFocusControl) {
this.previousFocusControl = previousFocusControl;
if (!shell.isVisible()) {
layoutShell();
quickAccessContents.preOpen();
shell.setVisible(true);
addAccessibleListener();
quickAccessContents.refresh(txtQuickAccess.getText().toLowerCase());
} else {
quickAccessContents.setShowAllMatches(!quickAccessContents.getShowAllMatches());
}
}
/**
* Checks if the text or shell has focus. If not, closes the shell.
*
* @param table
* the shell's table
* @param text
* the search text field
*/
protected void checkFocusLost(final Table table, final Text text) {
if (!shell.isDisposed() && !table.isDisposed() && !text.isDisposed()) {
if (table.getDisplay().getActiveShell() == table.getShell()) {
// If the user selects the trim shell, leave focus on the text
// so shell stays open
text.setFocus();
return;
}
if (!shell.isFocusControl() && !table.isFocusControl()
&& !text.isFocusControl()) {
quickAccessContents.doClose();
}
}
}
/**
* Adds a listener to the
* <code>org.eclipse.swt.accessibility.Accessible</code> object assigned to
* the Quick Access search box. The listener sets a name of a selected
* element in the search result list as a text to read for a screen reader.
*/
private void addAccessibleListener() {
if (accessibleListener == null) {
accessibleListener = new AccessibleAdapter() {
@Override
public void getName(AccessibleEvent e) {
e.result = selectedString;
}
};
txtQuickAccess.getAccessible().addAccessibleListener(accessibleListener);
}
}
/**
* Removes a listener from the
* <code>org.eclipse.swt.accessibility.Accessible</code> object assigned to
* the Quick Access search box.
*/
private void removeAccessibleListener() {
if (accessibleListener != null) {
txtQuickAccess.getAccessible().removeAccessibleListener(accessibleListener);
accessibleListener = null;
}
selectedString = ""; //$NON-NLS-1$
}
/**
* Notifies <code>org.eclipse.swt.accessibility.Accessible<code> object
* that selected item has been changed.
*/
private void notifyAccessibleTextChanged() {
if (table.getSelection().length == 0) {
return;
}
TableItem item = table.getSelection()[0];
selectedString = NLS.bind(QuickAccessMessages.QuickAccess_SelectedString, item.getText(0),
item.getText(1));
txtQuickAccess.getAccessible().sendEvent(ACC.EVENT_NAME_CHANGED, null);
}
private void restoreDialog() {
IDialogSettings dialogSettings = getDialogSettings();
if (dialogSettings != null) {
String[] orderedElements = dialogSettings.getArray(ORDERED_ELEMENTS);
String[] orderedProviders = dialogSettings.getArray(ORDERED_PROVIDERS);
String[] textEntries = dialogSettings.getArray(TEXT_ENTRIES);
String[] textArray = dialogSettings.getArray(TEXT_ARRAY);
try {
dialogHeight = dialogSettings.getInt(DIALOG_HEIGHT);
dialogWidth = dialogSettings.getInt(DIALOG_WIDTH);
} catch (NumberFormatException e) {
dialogHeight = -1;
dialogWidth = -1;
}
if (orderedElements != null && orderedProviders != null && textEntries != null
&& textArray != null) {
int arrayIndex = 0;
for (int i = 0; i < orderedElements.length; i++) {
QuickAccessProvider quickAccessProvider = providerMap.get(orderedProviders[i]);
int numTexts = Integer.parseInt(textEntries[i]);
if (quickAccessProvider != null) {
QuickAccessElement quickAccessElement = quickAccessProvider
.getElementForId(orderedElements[i]);
if (quickAccessElement != null) {
ArrayList<String> arrayList = new ArrayList<>();
for (int j = arrayIndex; j < arrayIndex + numTexts; j++) {
String text = textArray[j];
// text length can be zero for old workspaces,
// see bug 190006
if (text.length() > 0) {
arrayList.add(text);
elementMap.put(text, quickAccessElement);
}
}
textMap.put(quickAccessElement, arrayList);
previousPicksList.add(quickAccessElement);
}
}
arrayIndex += numTexts;
}
}
}
}
@PreDestroy
void dispose() {
storeDialog();
}
private void storeDialog() {
String[] orderedElements = new String[previousPicksList.size()];
String[] orderedProviders = new String[previousPicksList.size()];
String[] textEntries = new String[previousPicksList.size()];
ArrayList<String> arrayList = new ArrayList<>();
for (int i = 0; i < orderedElements.length; i++) {
QuickAccessElement quickAccessElement = previousPicksList.get(i);
ArrayList<String> elementText = textMap.get(quickAccessElement);
Assert.isNotNull(elementText);
orderedElements[i] = quickAccessElement.getId();
orderedProviders[i] = quickAccessElement.getProvider().getId();
arrayList.addAll(elementText);
textEntries[i] = elementText.size() + ""; //$NON-NLS-1$
}
String[] textArray = arrayList.toArray(new String[arrayList.size()]);
IDialogSettings dialogSettings = getDialogSettings();
dialogSettings.put(ORDERED_ELEMENTS, orderedElements);
dialogSettings.put(ORDERED_PROVIDERS, orderedProviders);
dialogSettings.put(TEXT_ENTRIES, textEntries);
dialogSettings.put(TEXT_ARRAY, textArray);
dialogSettings.put(DIALOG_HEIGHT, dialogHeight);
dialogSettings.put(DIALOG_WIDTH, dialogWidth);
}
private IDialogSettings getDialogSettings() {
final IDialogSettings workbenchDialogSettings = WorkbenchPlugin.getDefault()
.getDialogSettings();
IDialogSettings result = workbenchDialogSettings.getSection(getId());
if (result == null) {
result = workbenchDialogSettings.addNewSection(getId());
}
return result;
}
private String getId() {
return "org.eclipse.ui.internal.QuickAccess"; //$NON-NLS-1$
}
/**
* @param element
*/
private void addPreviousPick(String text, QuickAccessElement element) {
// previousPicksList:
// Remove element from previousPicksList so there are no duplicates
// If list is max size, remove last(oldest) element
// Remove entries for removed element from elementMap and textMap
// Add element to front of previousPicksList
previousPicksList.remove(element);
if (previousPicksList.size() == MAXIMUM_NUMBER_OF_ELEMENTS) {
Object removedElement = previousPicksList.removeLast();
ArrayList<String> removedList = textMap.remove(removedElement);
for (int i = 0; i < removedList.size(); i++) {
elementMap.remove(removedList.get(i));
}
}
previousPicksList.addFirst(element);
// textMap:
// Get list of strings for element from textMap
// Create new list for element if there isn't one and put
// element->textList in textMap
// Remove rememberedText from list
// If list is max size, remove first(oldest) string
// Remove text from elementMap
// Add rememberedText to list of strings for element in textMap
ArrayList<String> textList = textMap.get(element);
if (textList == null) {
textList = new ArrayList<>();
textMap.put(element, textList);
}
textList.remove(text);
if (textList.size() == MAXIMUM_NUMBER_OF_TEXT_ENTRIES_PER_ELEMENT) {
Object removedText = textList.remove(0);
elementMap.remove(removedText);
}
if (text.length() > 0) {
textList.add(text);
// elementMap:
// Put rememberedText->element in elementMap
// If it replaced a different element update textMap and
// PreviousPicksList
QuickAccessElement replacedElement = elementMap.put(text, element);
if (replacedElement != null && !replacedElement.equals(element)) {
textList = textMap.get(replacedElement);
if (textList != null) {
textList.remove(text);
if (textList.isEmpty()) {
textMap.remove(replacedElement);
previousPicksList.remove(replacedElement);
}
}
}
}
}
/**
* Returns the quick access shell for testing. Should not be referenced
* outside of the tests.
*
* @return the current quick access shell or <code>null</code>
*/
public Shell getQuickAccessShell() {
return shell;
}
/**
* Returns the quick access search text for testing. Should not be
* referenced outside of the tests.
*
* @return the search text in the workbench window or <code>null</code>
*/
public Text getQuickAccessSearchText() {
return txtQuickAccess;
}
/**
* Returns the table in the shell for testing. Should not be referenced
* outside of the tests.
*
* @return the table created in the shell or <code>null</code>
*/
public Table getQuickAccessTable(){
return table;
}
}